package domain

import (
	"fmt"
	"log"
	"net"
	"socket-im/global/consts"
	"socket-im/global/utils"
	"strings"
)

type Client struct {
	Name       string
	Addr       string
	Conn       net.Conn
	MsgChan    chan string
	Server     *Server
	ServerIp   string
	ServerPort int
}

func NewClient(ip string, port int) *Client {
	cli := &Client{
		ServerIp:   ip,
		ServerPort: port,
	}
	cli.Connect()
	return cli
}

func NewClientForNc(conn net.Conn, server *Server) *Client {
	addr := conn.RemoteAddr().String()
	cli := &Client{
		Name:    addr,
		Addr:    addr,
		MsgChan: make(chan string),
		Conn:    conn,
		Server:  server,
	}

	go cli.ListenMessage()
	return cli
}

func (cli *Client) Connect() {
	conn, err := net.Dial(consts.Tcp, utils.GetHost(cli.ServerIp, cli.ServerPort))
	if err != nil {
		log.Fatal("net.Dial err:", err)
		return
	}
	addr := conn.RemoteAddr().String()
	cli.Name = addr
	cli.Addr = addr
	cli.MsgChan = make(chan string)
	cli.Conn = conn
}

type Message struct {
	Origin  *Client
	Target  *Client
	Content string
}

func (cli *Client) Online() {
	server := cli.Server

	server.mapLock.Lock()
	server.OnlineMap[cli.Name] = cli
	server.mapLock.Unlock()

	server.BroadCast(cli, "loading")
}

func (cli *Client) Offline() {
	server := cli.Server

	server.mapLock.Lock()
	delete(server.OnlineMap, cli.Name)
	server.mapLock.Unlock()

	server.BroadCast(cli, "closing")
}

func (cli *Client) Handle(msg string) {
	if isFindUserAll(msg) {
		cli.responseAll()
	} else if isRename(msg) {
		cli.rename(msg[7:])
	} else if isWhisper(msg) {
		cli.WantToTalk(msg)
	} else {
		cli.Server.BroadCast(cli, msg)
	}
}

func (cli *Client) WantToTalk(msg string) {
	message := cli.getMessage(msg)
	cli.WhisperTo(message.Target, message.Content)
}

func (cli *Client) getListenerBy(msg string) *Client {
	message := cli.getMessage(msg)
	return message.Target
}

func (cli *Client) WhisperTo(listener *Client, msg string) {
	if listener == nil {
		cli.whisper("WhisperTo:" + getListenerName(msg) + " is offline")
	} else if msg == "" {
		cli.whisper("Please enter [to somebody something]")
	} else {
		listener.whisper("Receive:" + msg + " from " + cli.Name)
	}
}

func (cli *Client) getMessage(msg string) *Message {
	listener := getListenerName(msg)
	client, ok := cli.Server.OnlineMap[listener]
	if ok {
		return &Message{Origin: cli, Target: client, Content: getContent(msg)}
	}
	return &Message{Origin: cli, Target: nil, Content: getContent(msg)}
}

func getContent(msg string) string {
	content := strings.Split(msg, " ")
	if len(content) < 3 {
		return ""
	}
	return content[2]
}

func getListenerName(msg string) string {
	content := strings.Split(msg, " ")
	if len(content) < 2 {
		return ""
	}
	return content[1]
}

func isWhisper(msg string) bool {
	return len(msg) > 4 && strings.EqualFold("to ", msg[:3])
}

func (cli *Client) rename(name string) {
	_, used := cli.Server.OnlineMap[name]
	if used {
		cli.whisper("Rename:" + name +
			" had used! please input other name")
	} else {
		cli.Server.mapLock.Lock()
		old := cli.Name
		delete(cli.Server.OnlineMap, old)
		cli.Server.OnlineMap[name] = cli
		cli.Server.mapLock.Unlock()
		cli.Name = name
		cli.whisper("Rename: old-" + old + " to " + "new-" + name)
	}
}

func isRename(msg string) bool {
	return len(msg) > 7 && strings.EqualFold("rename ", msg[:7])
}

func (cli *Client) responseAll() {
	cli.Server.mapLock.Lock()
	var users []string
	for _, cur := range cli.Server.OnlineMap {
		users = append(users, cur.Name)
	}
	cli.Server.mapLock.Unlock()
	cli.whisper("Users:" + strings.Join(users, ","))
}

func (cli *Client) whisper(msg string) {
	_, _ = cli.Conn.Write([]byte(msg + "\n"))
}
func isFindUserAll(msg string) bool {
	return strings.EqualFold(msg, "who")
}

func (cli *Client) ListenMessage() {
	for {
		message := <-cli.MsgChan
		_, _ = cli.Conn.Write([]byte(message + "\n"))
	}

}

func (cli *Client) toString(message string) string {
	join := []string{cli.Addr, cli.Name, message}
	return join[0] + "-[" + join[1] + "]:" + join[2]
}

func ClientStart(ip string, port int) {
	client := NewClient(ip, port)
	fmt.Println("client:", *client, "connecting...")
	client.Connect()
	fmt.Println("client:", *client, "connected...")
	select {}
}
